iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
自我挑戰組

Ruby OOP to Oops !n 30系列 第 3

IT 邦鐵人賽 Day 3 - SRP

  • 分享至 

  • xImage
  •  

情境:

Ken:歐嚕,今天午餐妳覺得要吃什麼好呢?

Ken:已經月中了,是不是該開始吃土了勒?

歐嚕:喵喵 喵喵喵 喵

是不是對這段對話,感到匪夷所思呢!?
但這其中其實包含著程式語言內的單一職責與依賴關係… (りしれ供さ小)

請容我詳細介紹/images/emoticon/emoticon25.gif

單一職責(Single Responsibility Principle)

類別

或許大家注意到,昨天我們討論的物件導向,很強調的是訊息,不論是傳遞、接收與處理,作用對象都是訊息。
而Ruby的對象就是從類別所生成的,裡面的每一個方法,都是一個行為,而所回傳的就是訊息!
所以~我們該如何決定類別內有哪些方法呢(Method)
最簡單的判斷辦法就是問問看物件,這就很像我問歐嚕午餐吃什麼一樣,她不應該有辦法回應這個問題,因為這不該是她的責任(這裡的責任所代表的是合理性的行為而非義務等概念)。

class Cat
  def recommend_restaurant
    # 為什麼貓會有這個方法!?
  end
end

這時候有請我們Google大神,那怎樣設計類別才符合SRP呢?

A class should have only a single responsibility.

Robert C. Martin

A class should have only one reason to change.

而我認為最好理解的則是Sandi Metz書中提的 內聚(Cohesion) 來描述SRP

When everything in a class is related to its central purpose, the class is said to be highly cohesive or the have a single responsibility.

我的認知是,一個類別內所有的方法,都應該符合這類別的核心定位,就以貓類別來說

class Cat
  def hunt
  end
  
  def have_party_at_night
  end
end

不可否認的這兩個動作,都和符合貓的定位,而recommend_restaurant的方法應該存在於我的家人或者朋友(也可以是google大神)的類別才對。

而我第二個問題,是否要吃土? 這個問題只有我才會知道,所以程式的世界就是~

class Ken
  def lack_of_money?
    # 計算餘額
    # 回傳true或是false
  end
end

在Ken類別內有一個方法叫做lack_of_money?會去計算著我是否有足夠的$$來回傳true或者是false。

方法

那方法的單一責任又是怎麼設計呢?
那就是! 依賴行為而非資料

  1. 隱藏實例變數
  2. 隱藏資料結構

是不是有點難懂.../images/emoticon/emoticon06.gif
首先~ 還記得Ruby提供了attr_resder的方法嗎? 他直接將變數封裝成為方法。
不過為什麼我們不直接取用變數就好?
其中一項解釋就是方法成為了行為,讓這個行為遵守Don't repeat yourself(DRY)
讓我們回到剛剛的例子

class Ken
  attr_resder :money
  
  def initialize(money)
    @money = money
  end
  
  def lack_of_money?
    living_expenses = money - credit_card - ...
    living_expenses < 100 ? true : false
  end
 
end

如果我其他地方也需要使用到living_expenses時,就要重複寫同一行
living_expenses = money - credit_card - ...,或許這不是什麼大問題,但很麻煩... 更可怕的事情是在整份檔案我用了好幾十個living_expenses並且我的計算方式需要更改/images/emoticon/emoticon04.gif
那麼將living_expenses從區域變數改成@living_expenses實體變數,然後將計算放到initialize中呢? 感覺可行哦~?
回過頭來,仔細想想...若是這個類別自始至終都不需要這個數據,而卻每次new時都會多計算這段程式,感覺有些多此一舉呢

我想我們還是模仿Ruby的attr_resder的概念吧!

  def living_expenses
    @account_balances ||= money - credit_card - ...
  end

當需要修改計算方法,我們就只要改living_expenses就可以。所以我們仰賴living_expenses方法(行為),而非變數(資料)。 此外,在這段程式碼中,只有在第一次使用living_expenses會進行計算並存入@account_balances之中,當下次呼叫時,就不需重新計算,而是直接找到已經儲存在@account_balances的數值,這整個流程就是隱藏實例變數

那麼什麼是隱藏資料結構呢? 這時我們假設傳入的money不再是一個數字,而是money = [salary, red_envelope, refund]的資料結構時呢...?
@account_balances ||= money - credit_card - ...就會變成
@account_balances ||= money[0] - credit_card - ...,等下一個人來看發現money[0]
八成滿臉疑惑吧~
所以我們就改成

class Ken
  attr_reader :salary
  
  def initialize(money)
    @salary = money[0]
  end
  
  def lack_of_money?
    living_expenses < 100 ? true : false
  end
  
  private
  
  def living_expenses
    @account_balances ||= salary - credit_card - ...
  end
end

而這樣簡單的轉換就是隱藏資料結構,是不是變得比要好理解些呢!

話說昨天提到今天也要奉上依賴關係但好像篇幅太大了... 決定明日再來分享好囉!/images/emoticon/emoticon16.gif

感謝大家~ 如有問題,再煩請大家指教!


上一篇
IT 邦鐵人賽 Day 2 - OOP
下一篇
IT 邦鐵人賽 Day 4 - Dependencies
系列文
Ruby OOP to Oops !n 3020
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Jean_HSU
iT邦新手 5 級 ‧ 2022-09-20 20:25:58

頭香耶~

我要留言

立即登入留言